#include <typeinfo>
#include "FileRestore.h"
#include "nfs.h"
#include "input.h"
class FileRestore 
{
public:
    map<long,FileInode> files;
    map<long,FileInode> files2;
    Nfs *nfs;
    long fixfileNum;
    long maxWriteSize;      //转存文件最大写入大小 1G
    long maxFileSize;
    string toDir;
    
    FileRestore() {
        fixfileNum=0;
        maxWriteSize = 512*2*1024*1024;
        maxFileSize = 2*1000*1024*1024;
        toDir="./FileRestore/";
        char *d=ds->get(0);
        if (strncmp(d+3, "NTFS", 4) == 0){
            nfs=new Ntfs();
        }else{
            nfs=new Ext4();
        }
    }
    
    int getRoot(){
        return typeid(nfs)==typeid(Ntfs)  ? 5:2;
        //return files.count(5) ? 5:2;
    }
    
    long getParent(long index){
        return files[index].parent;
    }
    
    FileInode getFile(long index){
        if(files.count(index)==0) {
            FileInode file=nfs->getInode(index);
            if(file.postion !=-1) {
                files[index]=file;
            }
            return file;
        }
        return files[index];
    }
    
      /**
     * 给出文件的目录 
     */
    string getPwd(long index){
        string pwd;
        int i=0;
        while(index>10 && i++< 20){
            if(pwd.length()>0) pwd.insert(0,"/");
            FileInode file=this->getFile(index);
            pwd.insert(0,file.name);
            if(!files[index].parent || files[index].parent == index) break;
            index=files[index].parent;
            if(i>15) { cout << pwd<<endl; getchar(); }  
        }
        
        return pwd;
    }
    
    bool ls(long index){
        if(index ==-1) index=this->getRoot();
        FileInode file=this->getFile(index);
         if(file.postion==-1) {
            cout << "Err: no find this file:"<< index << endl;
            return false;
        }
        
        this->dumpFile(index);
        //正常文件  
        if(file.type==1){
//            cout <<  file.parent << "\t" <<file.postion<<"\t"<< (long)file.size  << "\t" << file.name  <<endl;            
            nfs->dump(file.postion,0x90);               //十六进制输出innode删除的前9行数据  
            ds->debug=true;                                        //输出扇区位置和读取长度信息  
            string data=nfs->getData(file,0,512);    //取一个扇区  
            ds->debug=false;
            ds->dump((char *)data.c_str());
            return true; 
        }
        
       //展示目录
        vector<long> *v1=&file.child;
        vector<long >::iterator it;
        
        //del repeat   去重
        map <string,long> namesMap;
        map <string,long>::iterator mit;
        list <long> a;
        for( it = v1->begin(); it != v1->end(); it++ ){
                FileInode f=this->getFile(*it);
                string name=f.name;
                char c= 49;
                if(namesMap.count(name)==0 || (namesMap.count(name)>0 && f.child.size()>0)){
                    if(namesMap.count(name)>0) name+="_"+ c++;
                    namesMap[name]=*it;
                }
        }
        for( mit = namesMap.begin(); mit != namesMap.end(); mit++ ){
            a.push_back(mit->second);
        }
        a.sort();
        
        cout <<   "inode号\t" << "文件类型(3:dir,1:file)|" <<  "父节点inode号|" << "所在位置\t" << "文件大小\t"   << "文件名\n";
        
        //输出  
        list <long>::iterator lit;
        for( lit = a.begin(); lit != a.end(); lit++ ){
                if(files.count(*lit)==0) {
                    cout <<  *lit << "\t lost \n";
                    continue;
                }
                FileInode f=files[*lit];
                string filename= f.name;
                if(f.type>1) {
                    filename+="/";
                    ostringstream s;   s<< f.child.size();
                    filename+=s.str();
                }
                char stype=' ';
                long size=f.size;
                if(size>1000000) { size/=1048576; stype='M';  } 
//                cout  <<f.parent << " ";
                cout <<  *lit << "\t" << f.type << "|" << f.parent<< "|" << f.postion/2048  << "M\t" << size  <<stype<< "\t" << filename  <<"\n";
        }
         cout << endl;
        return true;
    }
    
    /**
     * 搜索文件名 
     */
    long findByName(char *filename,long index=0){
        //目录形式 
        if(strstr(filename,"/") !=NULL ) {
            if(filename[strlen(filename)-1] =='/') filename[strlen(filename)-1]=0;
            return this->getSuggest(index,filename,filename);
        }
        
        map<long, FileInode>::iterator it = files.begin();
        int count=0;
        for( ; it != files.end(); it++ ){
            FileInode file=it->second;
//            if(!file.name) this->dumpFile(it->first);
            if(file.name && strstr(file.name,filename)) {
                count++;
                if(count<100) {
                    string name=file.name;  name=this->getPwd(it->first);
                    cout <<  it->first << "\t" << file.size << "\t" << name  << "\n";
                }
            }
        }
        cout << "共找到文件个数为：" << count << endl;
        
        return 0;
    }
    void dumpFile(long id){
                FileInode file = files[id];
                cout <<id;
                this->dumpFile(file);
    }
    static void dumpFile(FileInode file){
                    cout << "\n  parent:  "<< file.parent 
                         << "\n  type: " << file.type 
                        << "\n  size: " << file.size 
                        << "\n  name: " << file.name  
                        <<"\n  postion: "<< file.postion<< "("<<file.postion/2048<<"M)"
//                        <<"\n  child:["   ;
//                            vector<long >::iterator it;
//                            for( it = file.child.begin(); it != file.child.end(); it++ ){
//                                cout << *it<<",";
//                            }
//                        cout << "]" 
                                << endl;
    }
    
  

    /**
     * 根据文件索引  获取文件或文件夹  重新写入到其他目录  
    */    
    void restore(long  fileIndex,string toDir=""){
            if(toDir.size()==0) toDir=this->toDir;
            
            if(access(toDir.c_str(),0)==-1) mkdir(toDir.c_str(),0777);
            FileInode file=files[fileIndex];
            string fileName=toDir;
            fileName+=file.name;
            
             if(file.type == 1) { //文件
                 try{
                  string fileData;
                  if(file.size > this->maxWriteSize) {
                      if(file.size>this->maxFileSize) return file_put_content(fileName,fileData);    //文件大小过大
                      for(long i=0;i<file.size;i+=this->maxWriteSize){
                            printf("maxSize=%ld,i=%ld,size=%ld\n",this->maxWriteSize,i,file.size);                           
                            fileData= nfs->getData(file,i,this->maxWriteSize);
                            if(fileData.size()> file.size) fileData.resize(file.size);
                            file_put_content(fileName,fileData,"wb+");
                      }
                  }else{
                      fileData=nfs->getData(file);
                      if(file.size) fileData.resize(file.size);
                      if(fileData.size() < this->maxFileSize)
                            file_put_content(fileName,fileData);
                  }
                 printf("%s",file.name);
                 printf("\033[%dD\033[K",(int)strlen(file.name));
                         
                }catch(Exception *e) {
                   cerr<< e->msg;
               }
            }else {  //目录
                 if(kbhit()) {    //esc退出本次操作
                     char c=_getch();
                     if(c=='q') {
                         ungetc(c, stdin);
                         return;
                     }
                 }
                 cout << "\n"<<fileName; 
                   FileInode file= files[fileIndex];
                   vector<long> *v1=&file.child;
                   vector<long >::iterator it;
                   for( it = v1->begin(); it != v1->end(); it++ ){
                       this->restore((double)*it,fileName+"/");
                   }
            }
    }

    
    //寻找所有文件 
    void findAll() {
        while(true){
            long index=nfs->next();
            if(index == -1) break;
            FileInode file = nfs->getInode();
            
            if(files.count(index)>0) file.child=files[index].child;
            
            if(file.type==0 || file.type==2) {   //对于删除的文件归纳到4号文件下
                file.type=(file.type==0) ? 1:3;
                if(files[file.parent].type != 2 &&files[file.parent].parent!=4 ) file.parent=(file.type==1) ? 3: 4;   //文件到3, 目录到4
                files[4].type=3;  files[3].type=3;  //must add this ? 
            }
            int i0= files.count(index);
            FileInode f0= files[index];
            
            if(files.count(index)>0 && files[index].postion>0) {
                files2[index]=file;
            }else
                files[index] = file;
            if(file.parent)  
                    files[file.parent].child.push_back(index);
            
//            if(!file.parent)         
//            cout << "0000#####  " << index <<" parentId:"<< file.parent <<", name:"<< file.name <<", type:"<< file.type <<", size:"<< file.size << " #######" <<endl;
//            if(index>32) die(files[31].name);
            if(index%1000==0) {
                 printf("\33[2K\r");
                cout << index<<" " << file.name  <<" postion:"<< file.postion<<", size:"<< file.size;
            }
        }
        
        //合并file2到file1
        if(files2.size()>0) {
            cout << "重复id文件 合并 " << files2.size() <<endl;
            map<long,FileInode>::iterator it=files.end();
            long begin=it->first +100;
            foreach(files2,it) {
                files[begin + it->first]=it->second;
            }
        }        
       
        this->moveOutFile();
    }  
 
    /**
     * 
     * 搜集所有最终挂靠到根目录的文件  
     */
    void moveOutFile(){
         map<long, FileInode>::iterator it;
         it= files.end();    
         long index=it->first;
                
        for(it= files.begin() ; it != files.end(); it++ ){
            FileInode file=it->second;
            //存在孩子节点 并且 没有文件其他数据 或者 文件类型不是目录 则认为这里出现了一个 无法最终挂载到根目录的节点  
            if(file.child.size()>0 && (!file.name || file.type==1)) {
               char *fileName=(char *)malloc(20);   memset(fileName,0,sizeof(fileName)); 
               this->fixfileNum++;
                if(!file.name) {
                    sprintf(fileName,"%ld",it->first);
                    files[it->first].name=fileName;
                    files[it->first].parent=1;
                    files[it->first].size=0;
                    files[it->first].type=3;
                    continue;
                }
                
                FileInode addFile;
                sprintf(fileName,"%ld",++index);
                addFile.parent=1;
                addFile.type=3;
                addFile.size=0;
                addFile.name=fileName;
                addFile.child.assign(file.child.begin(),file.child.end());
                files[index]=addFile;
                files[1].child.push_back(index);
                
                cerr <<"找到被复写的文件目录："<<it->first <<" 被改写为:"<< index <<" 子目录个数为:"<< addFile.child.size() ;
                printf("\33[2K\r");
//                this->ls(index);

                
                file.child.empty();
            }
        }
         
         files[1].type=3;
         files[4].type=3;
    }
    
    void parsePostion(long postion) {
            FileInode file = nfs->getInode(postion);
            this->dumpFile(file);
    }
    
map<string,long> getSuggestList(long &index,const char *s){
        map<string,long> rt;
        FileInode file=files[index];
        vector<long> *v1=&file.child;
        foreach(*v1,it) {
            FileInode file=this->getFile(*it);
            if(strlen(s)>0  && strncmp(file.name,s,strlen(s))!=0) continue;
            rt[file.name]=*it;
//            cout <<file.name <<endl;
            if(strcmp(file.name,s)==0) {
                 index=*it;
                break;
            }
        }
        
        return rt;
    }
    /**
     * @params in    当前节点id
     * @params s      猜测依据   由于in的数值会被改变 而s只是in的一部分 所以in也会发生改变  
     * @params in    猜测结果 index
     */
    long getSuggest(long index, char *s,char *in=NULL){ 
        static map<string,long> rt; //存放猜测
        static string prefix;       //前缀符号 目录路径  
        static long bindex;     //上一次当前节点
        static char bs[255];  //上一次搜索串 
        static char sugIn[255];  //猜测的串
        static int continueIndex;  //连续tab列表偏移  
        
        if(strcmp(sugIn,s)==0 && strcmp(s,in)!=0) strcpy(s,bs);
        if(bindex!=index || strcmp(bs,s)!=0 || strcmp(s,in)==0) {
                rt.clear();
                prefix="";
                strcpy(bs,s);
                bindex=index;
                if(strstr(s,"/") !=NULL ) {
                    vector<string> paths=split(s,"/");
                    string str=paths.back(); paths.pop_back();
                    if(s[0]=='/') { index=this->getRoot(); paths.erase(paths.begin()); prefix="/"; }
                    foreach(paths,it) {
                        string sname=*it;
                        prefix=prefix+sname+"/";
                        if(sname.size()==1 && sname[0]=='.' ) continue;
                        if(sname.size()==2 && sname[0]=='.' && sname[1]=='.' ) { 
                            index=this->getParent(index);
                            continue;
                        }
                        if(!sname.size() || index<0) break;
                        this->getSuggestList(index,sname.c_str());
                    }
                    strcpy(s,str.c_str());
//                    strcpy(bs,s);
                }
                rt=this->getSuggestList(index,s);
                
                continueIndex=0;
        }
        strcpy(in,bs);
        if(rt.size()==0) return 0;
        int i=0;
        foreach(rt,it) {
            if(i++ == continueIndex){
                   string name=it->first;
                   strcpy(in,name.c_str());
                   index=it->second;
                   break;
            }
        }
        
        if(prefix.size()>0) {
            string strin=prefix+in;
            strcpy(in,strin.c_str());
        }
        
        strcpy(sugIn,in);
        if(++continueIndex>rt.size()-1) continueIndex=0;
        if(rt.size()==1 && files[index].type!=1 )    strcat(in,"/");
        
        return index;
    }
};
